# coding:utf-8
import binascii
import time

import ecdsa
from utils_lib.utils import sum256_hex, hash_public_key, address_to_pubkey_hash

subsidy = 1000


# 传输类
# by liang fei 2021.06.29


# 上传 upload
class TransactionUpload(object):

    # 以下通用:
    # source_node_address: 该tx的来源地址

    def __init__(self, source_node_address, source_node_reputation, public_key_hash=''):
        self.tx_type = 'upload'
        self.source_node_address = source_node_address  # public key
        self.source_node_reputation = source_node_reputation  # 原节点当前信誉值

        self.data = {}  # 数据存放容器 以dict形式存放
        self.metadata = ''  # 数据描述 可选项
        self.data_reputation = ''  # 所上传数据的信誉值

        self.signature = ''  # 签名 目前未应用-2021.09.15

        self.public_key_hash = public_key_hash  # 原有属性

    # 原有函数 加入自有地址
    def lock(self, address):
        hex_pub_key_hash = binascii.hexlify(address_to_pubkey_hash(address))
        self.public_key_hash = hex_pub_key_hash

    # 原有函数 检验
    def is_locked_with_key(self, pub_key_hash):
        return self.public_key_hash == pub_key_hash

    # 设置上传数据
    # data_bundle_obj (dict): 加密后的data_bundle dict
    def set_data(self, data_bundle_obj_dict):
        self.data = data_bundle_obj_dict
        return

    # obj -> dict
    def serialize(self):
        return self.__dict__

    # print
    def __repr__(self):
        print_str = '' \
            'TransactionUpload(' \
            'source_node_address={source_node_address}, ' \
            'source_node_reputation={source_node_reputation}, ' \
            'data={data}, ' \
            'metadata={metadata}, ' \
            'data_reputation={data_reputation}, ' \
            'public_key_hash={public_key_hash}' \
            ')'.format(
                source_node_address=str(self.source_node_address),
                source_node_reputation=str(self.source_node_reputation),
                data=str(self.data),
                metadata=str(self.metadata),
                data_reputation=str(self.data_reputation),
                public_key_hash=str(self.public_key_hash)
            )
        return print_str

    # dict -> obj
    @classmethod
    def deserialize(cls, data_dict):
        source_node_address = data_dict.get('source_node_address', '')
        source_node_reputation = data_dict.get('source_node_reputation', '')

        data = data_dict.get('data', {})
        metadata = data_dict.get('metadata', '')
        data_reputation = data_dict.get('data_reputation', '')

        signature = data_dict.get('signature', '')

        public_key_hash = data_dict.get('public_key_hash', '')

        transaction_sub_obj = cls(source_node_address, source_node_reputation, public_key_hash)
        transaction_sub_obj.data = data
        transaction_sub_obj.metadata = metadata
        transaction_sub_obj.data_reputation = data_reputation
        transaction_sub_obj.signature = signature

        return transaction_sub_obj

    # 为本交易子对象签名
    # 为transaction sign() 提供方法
    # previous_txs: Transaction object
    def sign(self, private_key):
        sign_hash = sum256_hex(str(self.serialize()))
        sign_key = ecdsa.SigningKey.from_string(binascii.a2b_hex(private_key), curve=ecdsa.SECP256k1)
        sign = sign_key.sign(sign_hash.encode())
        self.signature = binascii.hexlify(sign).decode()

    # 验证本对象签名 即验证签名是否和声称的地址相符
    def verify(self):
        # sign = binascii.unhexlify(self.signature)
        # verify_key = ecdsa.VerifyingKey.from_string(binascii.a2b_hex(self.public_key_hash), curve=ecdsa.SECP256k1)
        # print(self.public_key_hash)
        # print(self.signature)

        # _tem = binascii.a2b_hex(binascii.b2a_hex(self.public_key_hash.encode()))
        # _tem = self.public_key_hash
        # verify_key = ecdsa.VerifyingKey.from_string(_tem, curve=ecdsa.SECP256k1)

        # vk = ecdsa.VerifyingKey.from_string(binascii.a2b_hex(self.source_node_address), curve=ecdsa.SECP256k1)
        #
        # if not verify_key.verify(sign, self.serialize()):
        #     return False
        # return True
        return

    # 验证之前交易流程 只验证前一个交易即可
    # 验证本交易指向的前向交易是否为真
    def verify_previous_tx(self, previous_tx):
        return True


# 请求 Request
class TransactionRequest(object):

    def __init__(self, source_node_address, source_node_reputation, public_key_hash='',
                 source_rsa_public_key='', previous_tx_id=''):

        self.tx_type = 'request'

        self.source_node_address = source_node_address  # 以下通用为该tx来源地址
        self.source_node_reputation = source_node_reputation  # 原节点当前信誉值

        self.source_rsa_public_key = source_rsa_public_key  # 来源地址的非对称加密公钥 用于传输后续对称秘钥

        self.previous_tx_id = previous_tx_id  # 请求数据的块的tx_id

        self.signature = ''  # 同上 目前未应用

        self.public_key_hash = public_key_hash  # 原有属性

    # 原有函数 加入自有地址
    def lock(self, address):
        hex_pub_key_hash = binascii.hexlify(address_to_pubkey_hash(address))
        self.public_key_hash = hex_pub_key_hash

    # 原有函数 检验
    def is_locked_with_key(self, pub_key_hash):
        return self.public_key_hash == pub_key_hash

    def serialize(self):
        return self.__dict__

    # 需要修改
    def __repr__(self):
        print_str = '' \
            'TransactionRequest(' \
            'source_node_address={source_node_address}, ' \
            'source_node_reputation={source_node_reputation}, ' \
            'source_rsa_public_key={source_rsa_public_key}, ' \
            'previous_tx_id={previous_tx_id}, ' \
            'public_key_hash={public_key_hash}' \
            ')'.format(
                source_node_address=str(self.source_node_address),
                source_node_reputation=str(self.source_node_reputation),
                source_rsa_public_key=str(self.source_rsa_public_key),
                previous_tx_id=str(self.previous_tx_id),
                public_key_hash=str(self.public_key_hash)
            )
        return print_str

    @classmethod
    def deserialize(cls, data):
        source_node_address = data.get('source_node_address', '')
        source_node_reputation = data.get('source_node_reputation', '')

        source_rsa_public_key = data.get('source_rsa_public_key', '')
        previous_tx_id = data.get('previous_tx_id', '')

        signature = data.get('self.signature', '')
        public_key_hash = data.get('public_key_hash', '')

        transaction_sub_obj = cls(source_node_address, source_node_reputation, public_key_hash,
                                  source_rsa_public_key, previous_tx_id)
        transaction_sub_obj.signature = signature

        return transaction_sub_obj

    # 目前未应用 预计将来也不会应用
    def sign(self, private_key):
        sign_hash = sum256_hex(str(self.serialize()))
        sign_key = ecdsa.SigningKey.from_string(binascii.a2b_hex(private_key), curve=ecdsa.SECP256k1)
        sign = sign_key.sign(sign_hash.encode())
        self.signature = binascii.hexlify(sign).decode()

    # 同上不应用
    def verify(self):
        sign = binascii.unhexlify(self.signature)
        verify_key = ecdsa.VerifyingKey.from_string(binascii.a2b_hex(self.public_key_hash), curve=ecdsa.SECP256k1)
        if not verify_key.verify(sign, self.serialize()):
            return False
        return True

    # 验证之前交易流程 只验证前一个交易即可
    # 验证本交易指向的前向交易是否为真
    def verify_previous_tx(self, previous_tx):
        result = True
        if previous_tx.tx_type != 'upload':
            result = False
        if self.previous_tx_id != previous_tx.tx_id:
            result = False
        return result


# 共享 Share
class TransactionShare(object):

    def __init__(self, source_node_address, source_node_reputation, public_key_hash='', previous_tx_id=''):
        self.tx_type = 'share'

        self.source_node_address = source_node_address  # 以下通用为该tx来源地址
        self.source_node_reputation = source_node_reputation  # 原节点当前信誉值

        self.key_ciphertext = ''

        self.previous_tx_id = previous_tx_id  # 前向tx的id

        self.signature = ''

        self.public_key_hash = public_key_hash  # 原有属性

    # ciphertext: <str>
    def set_key_ciphertext(self, ciphertext):
        self.key_ciphertext = ciphertext

    # 原有函数 加入自有地址
    def lock(self, address):
        hex_pub_key_hash = binascii.hexlify(address_to_pubkey_hash(address))
        self.public_key_hash = hex_pub_key_hash

    # 原有函数 检验
    def is_locked_with_key(self, pub_key_hash):
        return self.public_key_hash == pub_key_hash

    def serialize(self):
        return self.__dict__

    # 需修改
    def __repr__(self):
        print_str = '' \
            'TransactionShare(' \
            'source_node_address={source_node_address}, ' \
            'source_node_reputation={source_node_reputation}, ' \
            'key_ciphertext={key_ciphertext}, ' \
            'previous_tx_id={previous_tx_id}, ' \
            'public_key_hash={public_key_hash}' \
            ')'.format(
                source_node_address=str(self.source_node_address),
                source_node_reputation=str(self.source_node_reputation),
                key_ciphertext=str(self.key_ciphertext),
                previous_tx_id=str(self.previous_tx_id),
                public_key_hash=str(self.public_key_hash)
            )
        return print_str

    @classmethod
    def deserialize(cls, data):
        source_node_address = data.get('source_node_address', '')
        source_node_reputation = data.get('source_node_reputation', '')

        key_ciphertext = data.get('key_ciphertext', '')
        previous_tx_id = data.get('previous_tx_id', '')

        signature = data.get('self.signature', '')
        public_key_hash = data.get('public_key_hash', '')

        transaction_sub_obj = cls(source_node_address, source_node_reputation, public_key_hash, previous_tx_id)
        transaction_sub_obj.set_key_ciphertext(key_ciphertext)
        transaction_sub_obj.signature = signature

        return transaction_sub_obj

    def sign(self, private_key):
        sign_hash = sum256_hex(str(self.serialize()))
        sign_key = ecdsa.SigningKey.from_string(binascii.a2b_hex(private_key), curve=ecdsa.SECP256k1)
        sign = sign_key.sign(sign_hash.encode())
        self.signature = binascii.hexlify(sign).decode()

    def verify(self):
        sign = binascii.unhexlify(self.signature)
        verify_key = ecdsa.VerifyingKey.from_string(binascii.a2b_hex(self.public_key_hash), curve=ecdsa.SECP256k1)
        if not verify_key.verify(sign, self.serialize()):
            return False
        return True

    # 验证之前交易流程 只验证前一个交易即可
    def verify_previous_tx(self, previous_tx):
        result = True
        if previous_tx.tx_type != 'request':
            result = False
        if self.previous_tx_id != previous_tx.tx_id:
            result = False
        return result


# 评价 Evaluate
class TransactionEvaluate(object):

    def __init__(self, source_node_address, source_node_reputation, public_key_hash='',
                 previous_tx_id='', data_evaluate=0, data_source_node_evaluate=0):
        self.tx_type = 'evaluate'

        self.source_node_address = source_node_address  # 以下通用为该tx来源地址
        self.source_node_reputation = source_node_reputation  # 原节点当前信誉值

        self.previous_tx_id = previous_tx_id  # 前向tx的id

        self.data_evaluate = data_evaluate  # 对数据的评价
        self.data_source_node_evaluate = data_source_node_evaluate  # 对数据来源节点节点的评价

        self.signature = ''

        self.public_key_hash = public_key_hash  # 原有属性

    # 原有函数 加入自有地址
    def lock(self, address):
        hex_pub_key_hash = binascii.hexlify(address_to_pubkey_hash(address))
        self.public_key_hash = hex_pub_key_hash

    # 原有函数 检验
    def is_locked_with_key(self, pub_key_hash):
        return self.public_key_hash == pub_key_hash

    def serialize(self):
        return self.__dict__

    # 需修改
    def __repr__(self):
        print_str = '' \
            'TransactionRequest(' \
            'source_node_address={source_node_address}, ' \
            'source_node_reputation={source_node_reputation}, ' \
            'previous_tx_id={previous_tx_id}, ' \
            'data_evaluate={data_evaluate}, '\
            'data_source_node_evaluate={data_source_node_evaluate}, ' \
            'public_key_hash={public_key_hash}' \
            ')'.format(
                source_node_address=str(self.source_node_address),
                source_node_reputation=str(self.source_node_reputation),
                previous_tx_id=str(self.previous_tx_id),
                data_evaluate=str(self.data_evaluate),
                data_source_node_evaluate=str(self.data_source_node_evaluate),
                public_key_hash=str(self.public_key_hash)
            )
        return print_str

    @classmethod
    def deserialize(cls, data):
        source_node_address = data.get('source_node_address', '')
        source_node_reputation = data.get('source_node_reputation', '')

        previous_tx_id = data.get('previous_tx_id', '')

        data_evaluate = data.get('data_evaluate', 0)
        data_source_node_evaluate = data.get('data_source_node_evaluate', 0)

        signature = data.get('self.signature', '')
        public_key_hash = data.get('public_key_hash', '')

        transaction_sub_obj = cls(source_node_address, source_node_reputation, public_key_hash, previous_tx_id,
                                  data_evaluate, data_source_node_evaluate)
        transaction_sub_obj.signature = signature

        return transaction_sub_obj

    def sign(self, private_key):
        sign_hash = sum256_hex(str(self.serialize()))
        sign_key = ecdsa.SigningKey.from_string(binascii.a2b_hex(private_key), curve=ecdsa.SECP256k1)
        sign = sign_key.sign(sign_hash.encode())
        self.signature = binascii.hexlify(sign).decode()

    def verify(self):
        sign = binascii.unhexlify(self.signature)
        verify_key = ecdsa.VerifyingKey.from_string(binascii.a2b_hex(self.public_key_hash), curve=ecdsa.SECP256k1)
        if not verify_key.verify(sign, self.serialize()):
            return False
        return True

    # 验证之前交易流程 只验证前一个交易即可
    def verify_previous_tx(self, previous_tx):
        result = True
        if previous_tx.tx_type != 'share':
            result = False
        if self.previous_tx_id != previous_tx.tx_id:
            result = False
        return result


# upload 上传
# request 请求
# share 共享（授权）
# evaluate 评价
class Transaction(object):

    # data_id: 只有TransactionUpload Object会有一个data_id
    # business_id: 同一组的四个业务同属于一个business 拥有相同id

    def __init__(self, transaction_sub_obj):
        self.tx_id = ''  # 3: transaction id
        self.previous_tx_id = ''

        self.tx_content = transaction_sub_obj  # 1: transaction_sub_object
        self.tx_type = transaction_sub_obj.tx_type  # 2: transaction type

        self.data_id = ''  # 上传的数据id
        self.business_id = ''  # 该组业务id

        self.tx_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())

        self.signature = ''  # 签名

    # 子类型字典 类型改动时在此添加即可
    GLOBAL_TRANSACTION_SUB_MODEL = {
        'upload': TransactionUpload,
        'request': TransactionRequest,
        'share': TransactionShare,
        'evaluate': TransactionEvaluate,
    }

    # 设置tx_id
    def set_id(self):
        _sign_data = str(self.previous_tx_id) + str(self.tx_content.serialize()) + str(self.tx_type) \
                     + str(self.data_id) + str(self.business_id)
        self.tx_id = sum256_hex(_sign_data)  # 3: transaction id

    def set_data_id(self, _data_id):
        self.data_id = _data_id
        return

    def set_business_id(self, _business_id=''):
        if self.tx_type == 'upload':
            return
        self.business_id = _business_id
        return

    def set_previous_tx_id(self):
        if self.tx_type == 'upload':
            return
        self.previous_tx_id = self.tx_content.previous_tx_id
        return

    def serialize(self):
        # a = self.tx_content.serialize()
        # print(a)
        # print(type(a))
        return {
            'tx_id': self.tx_id,
            'previous_tx_id': self.previous_tx_id,
            'tx_content': self.tx_content.serialize(),
            'tx_type': self.tx_type,
            'data_id': self.data_id,
            'business_id': self.business_id,
            'tx_time': self.tx_time,
            'signature': self.signature,
        }

    # 反序列化 dict -> object
    @classmethod
    def deserialize(cls, data):
        tx_id = data.get('tx_id', '')
        previous_tx_id = data.get('previous_tx_id', '')

        tx_content = data.get('tx_content', '')
        tx_type = data.get('tx_type', '')

        data_id = data.get('data_id', '')
        business_id = data.get('business_id', '')
        tx_time = data.get('tx_time', '')

        signature = data.get('signature', '')

        transaction_sub_type = cls.GLOBAL_TRANSACTION_SUB_MODEL.get(tx_type, 0)
        if transaction_sub_type == 0:
            return 'Wrong type.'
        else:
            transaction_sub_obj = transaction_sub_type.deserialize(tx_content)
            transaction_obj = cls(transaction_sub_obj)
            transaction_obj.tx_type = tx_type

            transaction_obj.tx_id = tx_id
            transaction_obj.previous_tx_id = previous_tx_id

            transaction_obj.data_id = data_id
            transaction_obj.business_id = business_id
            transaction_obj.tx_time = tx_time
            transaction_obj.signature = signature

            return transaction_obj

    # 在本系统中仅充当整个chain的第一个块
    @classmethod
    def coinbase_tx(cls, to, data):
        transaction_upload_obj = TransactionUpload('coinbase-address', -1, to)
        transaction_obj = cls(transaction_upload_obj)
        transaction_obj.set_id()
        transaction_obj.set_data_id('0')
        return transaction_obj

    # 判定是否为首块交易 与上述函数相对应
    def is_coinbase(self):
        if self.tx_content.tx_type != 'upload':
            return False
        if self.tx_content.source_node_address != 'coinbase-address':
            return False
        if self.tx_content.source_node_reputation != -1:
            return False
        return True

    def __repr__(self):
        print_str = '' \
            'Transaction(' \
            'tx_id={tx_id}, previous_tx_id={previous_tx_id}, ' \
            'tx_content={tx_content}, data_id={data_id}, business_id={business_id}' \
            ')'.format(
                tx_id=self.tx_id, previous_tx_id=self.previous_tx_id, tx_content=self.tx_content,
                data_id=self.data_id, business_id=self.business_id
            )
        return print_str

    # 对交易签名
    def sign(self, private_key, previous_tx=None):
        if self.is_coinbase():
            return

        self.set_id()

        sk = ecdsa.SigningKey.from_string(binascii.a2b_hex(private_key), curve=ecdsa.SECP256k1)
        sign = sk.sign(self.tx_id.encode())
        self.signature = binascii.hexlify(sign).decode()

    # 验证tx正确性
    def verify(self, previous_tx):

        sign = binascii.unhexlify(self.signature)
        vk = ecdsa.VerifyingKey.from_string(binascii.a2b_hex(self.tx_content.source_node_address),
                                            curve=ecdsa.SECP256k1)
        if not vk.verify(sign, self.tx_id.encode()):
            return False

        return True

    # 验证所指向的前向交易是否为传入的tx
    # previous_tx: transaction obj
    def verify_previous_tx(self, previous_tx=None):
        if previous_tx:
            return previous_tx.tx_content.verify_previous_tx()
        else:
            return True
